/* 
 * File:   Application.cpp
 * Author: cjjonker
 * 
 * Created on 20 december 2012, 15:03
 */

#include "Application.h"
#include <iostream>
#include <GL/glut.h>
#include "Utilities.h"
#include <GL/glui.h>
#include "Visualizer.h"

using namespace std;

Simulation Application::simulation;			// the smoke simulation
Visualization Application::visualization;	// visualization of the simulation

//size of the graphics window, in pixels
int Application::winWidth;
int Application::winHeight;	

bool Application::setSeed;

GLUI* Application::glui;						// user interface
int Application::main_window;

GLUI_Button *pause_button;

vector<GLUI_Checkbox*> drawItCheckboxes;
vector<GLUI_Checkbox*> alphaBlendCheckboxes;
vector<GLUI_Checkbox*> normalizeCheckboxes;
vector<GLUI_Listbox*> colorModeListboxes;
vector<GLUI_Listbox*> scaleModeListboxes;
vector<GLUI_Listbox*> colorMapDatasetListboxes;
vector<GLUI_Listbox*> alphaDatasetListboxes;
vector<GLUI_Listbox*> datasetListboxes;
vector<GLUI_Listbox*> appearanceListboxes;

vector<GLUI_Spinner*> hueSpinners;
vector<GLUI_Spinner*> satSpinners;
vector<GLUI_Spinner*> valSpinners;
vector<GLUI_Spinner*> levelSpinners;
vector<GLUI_Spinner*> clampMaxSpinners;
vector<GLUI_Spinner*> clampMinSpinners;

vector<GLUI_Spinner*> vectorScaleSpinners;
vector<GLUI_Spinner*> xGridSpinners;
vector<GLUI_Spinner*> yGridSpinners;

vector<GLUI_Button*> removeSeedButtons;

GLUI_Spinner* slicesSpinner;
GLUI_Spinner* slicesDTSpinner;
GLUI_Spinner* globalAlphaSpinner;



float hsvmin = 0.0;
float hsvmax = 1.0;

int levelmin = 2;
int levelmax = 255;

int clampFunction = GLUI_LIMIT_CLAMP;

float clampMin = 0;
float clampMax = 20;

// Minimal distance between user set clampMin and clampMax < 0.1 = ERROR!
float clampRange = 0.1;

// Length of timeslice buffer for simulation.
int buffersize = 1000;

int defaultSlices = 1;
int defaultSliceDT = 10;
float defaultGlobalAlpha = 1.0;

float defaultXTrans = 0.0;
float defaultYTrans = 0.0;
float defaultZTrans = 110.0;

float *xtrans = new float(defaultXTrans);
float *ytrans = new float(defaultYTrans);
float *ztrans = new float(defaultZTrans);

float rotation [16];



int *timeslices = new int(defaultSlices);
int *slice_dt = new int(defaultSliceDT);
float *globalAlpha = new float(defaultGlobalAlpha);


void Application::update()
{
    glutSetWindow(main_window);
    simulation.do_one_simulation_step();
    glutPostRedisplay();
}

void Application::initialize(int *argc, char** argv)
{	
    resetRotation();
    glutInit(argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(1000, 800);
    main_window = glutCreateWindow("Real-time smoke simulation and visualization");

    // blie bloe bla

    // pass static functions as callback to GLUT
    glutDisplayFunc(display);
    glutMotionFunc(drag);
    glutKeyboardFunc(keyboard);
    glutMouseFunc(mouseHandler);

    // pass static functions as callback to GLUI
    GLUI_Master.set_glutReshapeFunc(reshape);  
    GLUI_Master.set_glutIdleFunc(update);

    simulation.init_simulation(Simulation::DIM,buffersize);	//initialize the simulation data structures
    initUI();   
    
    visualization.setSimulation(&simulation);
    
    setWidgets();	
    glutMainLoop();							// enter main loop
    
}

//reshape: Handle window resizing (reshaping) events
void Application::reshape(int w, int h)
{
    glViewport(0.0f, 0.0f, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    //glOrtho(-(GLdouble)w / 1.5, (GLdouble)w / 1.5, -1.2 * (GLdouble)h / 2.0, 1.2 * (GLdouble)h / 2.0, -1000.0, 1000.0);
    gluPerspective(0.25, float(w) / h, 1.5, 2000);
    winWidth = w; 
    winHeight = h;
}

// output usage instructions
void Application::outputUsage()
{
    cout << "Fluid Flow Simulation and Visualization\n";
    cout << "=======================================\n";
    cout << "Click and drag the mouse to steer the flow!\n";
    cout << "T/t:   increase/decrease simulation timestep\n";
    cout << "S/s:   increase/decrease hedgehog scaling\n";
    cout << "c:     toggle direction coloring on/off\n";
    cout << "V/v:   increase decrease fluid viscosity\n";
    cout << "x:     toggle drawing matter on/off\n";
    cout << "y:     toggle drawing hedgehogs on/off\n";
    cout << "m:     toggle thru scalar coloring\n";
    cout << "a:     toggle the animation on/off\n";
    cout << "q:     quit\n\n";	
}

//keyboard: Handle key presses
void Application::keyboard(unsigned char key, int x, int y)
{
    switch (key)
    {
        case 't': { simulation.change_timestep(-0.001); } break;
        case 'T': { simulation.change_timestep(0.001); } break;
        /*
        case 'c': { visualization.toggle(Visualization::UseDirectionColoring); } break;
        case 'S': { visualization.scale_hedgehogs(1.2); } break;
        case 's': { visualization.scale_hedgehogs(0.8); } break;
         */
        case 'V': { simulation.scale_viscosity(5.0); } break;
        case 'v': { simulation.scale_viscosity(0.2); } break;
        case 'e': { setSeed = true; } break;
/*
        case 'x': 
        {
                visualization.toggle(Visualization::DrawSmoke);		
                if(visualization.is_enabled(Visualization::DrawSmoke))
                        visualization.enable(Visualization::DrawVectorField);				  		  
        } break;

        case 'y': 
        {				
            visualization.toggle(Visualization::DrawVectorField);		
            if(visualization.is_enabled(Visualization::DrawVectorField))
                    visualization.enable(Visualization::DrawSmoke);		
        } break;
*/
        case 'm': { } break;

        case 'a': { simulation.toggle_frozen(); } break;

        case 'q': { quit(); } break;

        default: {} break;
    }

    // sync live variables
    GLUI_Master.sync_live_all();	
}

// drag: When the user drags with the mouse, add a force that corresponds to the direction of the mouse
//       cursor movement. Also inject some new matter into the field at the mouse location.
void Application::drag(int mx, int my)
{
    int xi, yi, X, Y; 
    double  dx, dy, len;
    static int lmx=0,lmy=0;				//remembers last mouse location

    // Compute the array index that corresponds to the cursor location
    xi = (int)clamp((double)(Simulation::DIM + 1) * ((double)mx / (double)winWidth));
    yi = (int)clamp((double)(Simulation::DIM + 1) * ((double)(winHeight - my) / (double)winHeight));

    X = xi; Y = yi;

    if (X > (Simulation::DIM - 1))  
        X = Simulation::DIM - 1; 

    if (Y > (Simulation::DIM - 1))  
        Y = Simulation::DIM - 1;

    if (X < 0) X = 0; if (Y < 0) Y = 0;
    
    
    {
        // Add force at the cursor location
        my = winHeight - my;
        dx = mx - lmx; dy = my - lmy;
        len = sqrt(dx * dx + dy * dy);
        if (len != 0.0) {  dx *= 0.1 / len; dy *= 0.1 / len; }
        simulation.insert_force(X, Y, dx, dy);	
        lmx = mx; 
        lmy = my;
    }
}

void Application::mouseHandler(int button, int state, int mx, int my)
{    // Compute the array index that corresponds to the cursor location
    
    
    if (setSeed && button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    {
        int xi = double(Simulation::DIM + 1) * double(mx) / double(winWidth);
        int yi = double(Simulation::DIM + 1) * double(winHeight - my) / double(winHeight);
        visualization.setSeed(xi, yi);
        setSeed = false;
    }
    
}

void Application::buttonHandler(int id)
{
    switch(id)
    {
        case QuitButton : { quit(); } break;
        case PauseButton: { pause(); } break;
        case SeedButton: {setSeed = true; } break;        
        case ResetViewButton: { resetView(); } break;
        default: {} break;
    }
}

void Application::drawItCheckBoxHandler(int id)
{
    visualization.getVisualizers().at(id)->setActive(drawItCheckboxes.at(id)->get_int_val());
}

void Application::alphaBlendCheckboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setUsesAlpha(alphaBlendCheckboxes.at(id)->get_int_val());
}

void Application::colorModeListboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setCurrentMapColormode(colorModeListboxes.at(id)->get_int_val());
}

void Application::colorMapDatasetListboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setCurrentMapDataset(colorMapDatasetListboxes.at(id)->get_int_val());
}

void Application::alphaDatasetListboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setCurrentAlphaDataset(alphaDatasetListboxes.at(id)->get_int_val());
}

void Application::scaleModeListboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setCurrentMapScalemode(scaleModeListboxes.at(id)->get_int_val());
}

void Application::datasetListboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setCurrentVectorDataset(datasetListboxes.at(id)->get_int_val());
}

void Application::valSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setColormapVal(valSpinners.at(id)->get_float_val());
}

void Application::hueSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setColormapHue(hueSpinners.at(id)->get_float_val());
}

void Application::satSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setColormapSat(satSpinners.at(id)->get_float_val());
}

void Application::levelSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setColormapLevels(levelSpinners.at(id)->get_int_val());
}

void Application::appearanceListboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setCurrentAppearance(appearanceListboxes.at(id)->get_int_val());
}

void Application::normalizeCheckboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setNormalize(normalizeCheckboxes.at(id)->get_int_val());
}

void Application::clampMaxSpinnerHandler(int id)
{
    clampMaxSpinners.at(id)->set_float_limits(clampMinSpinners.at(id)->get_float_val()+clampRange, clampMax, clampFunction);
    clampMinSpinners.at(id)->set_float_limits(clampMin, clampMaxSpinners.at(id)->get_float_val()-clampRange, clampFunction);
    visualization.getVisualizers().at(id)->setCurrentMapClampMax(clampMaxSpinners.at(id)->get_float_val());
}

void Application::clampMinSpinnerHandler(int id)
{
    clampMaxSpinners.at(id)->set_float_limits(clampMinSpinners.at(id)->get_float_val()+clampRange, clampMax, clampFunction);
    clampMinSpinners.at(id)->set_float_limits(clampMin, clampMaxSpinners.at(id)->get_float_val()-clampRange, clampFunction);
    visualization.getVisualizers().at(id)->setCurrentMapClampMin(clampMinSpinners.at(id)->get_float_val());
}

void Application::vectorScaleSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setVectorScale(vectorScaleSpinners.at(id)->get_float_val());
}

void Application::xGridSizeSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setXGridSize(xGridSpinners.at(id)->get_int_val());
}

void Application::yGridSizeSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setYGridSize(yGridSpinners.at(id)->get_int_val());
}

void Application::removeSeedsButtonHandler(int id)
{
    visualization.getVisualizers().at(id)->removeAllSeeds();
}

void Application::seedGridButtonHandler(int id)
{
    visualization.getVisualizers().at(id)->seedGrid();
}

void Application::setSliceLimits(int dummy)
{
    int maxSlices = floor((buffersize)/(*slice_dt));
    int maxSliceDT = floor((buffersize)/(*timeslices));
    slicesSpinner->set_int_limits(1,maxSlices,clampFunction);
    slicesDTSpinner->set_int_limits(1,maxSliceDT,clampFunction);
}




void Application::initUI()
{
    const int width = 400;
    setSeed = false;
    glui = GLUI_Master.create_glui("Controls");
    glui->set_main_gfx_window(main_window);   
    
    GLUI_Panel *sim_panel = glui->add_panel("Simulation options");
    
    pause_button = new GLUI_Button(sim_panel, "Freeze", PauseButton, buttonHandler); 
    new GLUI_Button(sim_panel, "Quit", QuitButton, buttonHandler);
    
    GLUI_Panel* vis_options_panel = glui->add_panel("Visualization options");
    
    slicesSpinner = new GLUI_Spinner(vis_options_panel,"# timeslices",GLUI_SPINNER_INT,timeslices,0,setSliceLimits);
    slicesDTSpinner = new GLUI_Spinner(vis_options_panel,"timeslices dt",GLUI_SPINNER_INT,slice_dt,0,setSliceLimits);
    setSliceLimits(0);
    globalAlphaSpinner = new GLUI_Spinner(vis_options_panel, "Global alpha: ",
            GLUI_SPINNER_FLOAT, globalAlpha);
    globalAlphaSpinner->set_float_limits(0.5, 1.5, GLUI_LIMIT_CLAMP);
    
    GLUI_Panel* nav_panel = glui->add_panel("Navigation");
    
    glui->add_rotation_to_panel(nav_panel,"Rotation", rotation);
    
    glui->add_translation_to_panel(nav_panel,"X", GLUI_TRANSLATION_X, xtrans);
    glui->add_translation_to_panel(nav_panel,"Y", GLUI_TRANSLATION_Y, ytrans);
    glui->add_translation_to_panel(nav_panel,"Z", GLUI_TRANSLATION_Z, ztrans);
    
    glui->add_button_to_panel(nav_panel,"Reset View",ResetViewButton,buttonHandler);
       
    glui->add_column(false);
    GLUI_Panel* visualizers_panel = glui->add_panel("Visualizations");
    for (int i = 0;
            i != visualization.getVisualizers().size(); ++i)
    {
        Visualizer *vis = visualization.getVisualizers().at(i);
        
        const char *visualizername = vis->getName().c_str();
        
        GLUI_Rollout* currentRollout =glui->add_rollout_to_panel(visualizers_panel, visualizername, true);
        
        drawItCheckboxes.push_back(glui->add_checkbox_to_panel(currentRollout,"Draw it",NULL, i,drawItCheckBoxHandler));
        
        
        if (vis->hasMultipleAppearances())
        {
            appearanceListboxes.push_back(
            makeListboxFromVector(vis->getAppearancesNames(), "Appearance", i, currentRollout, appearanceListboxHandler)
                    );
        }
        else
        {
            appearanceListboxes.push_back(NULL);
        }
        
        // this part is only for vector visualizers
        if (vis->usesVectorDataset())
        {
            GLUI_Panel* vectorOptionsPanel = new GLUI_Panel(currentRollout, "Vector Options");
            datasetListboxes.push_back(
                makeListboxFromVector(vis->getVectorDatasetNames(), "Vector data", i, vectorOptionsPanel, datasetListboxHandler)
                );
                
            GLUI_Spinner* currentScaleSpinner = glui->add_spinner_to_panel(vectorOptionsPanel,"Vector scale",GLUI_SPINNER_FLOAT,NULL,i,vectorScaleSpinnerHandler);
            
            currentScaleSpinner->set_float_limits(0.1,10,GLUI_LIMIT_CLAMP);
            currentScaleSpinner->set_float_val(1.0);       
            vectorScaleSpinners.push_back(currentScaleSpinner);
            
            GLUI_Spinner* xGridSpinner = glui->add_spinner_to_panel(vectorOptionsPanel,
                    "Grid size (X)", GLUI_SPINNER_INT, NULL, i, xGridSizeSpinnerHandler);
            GLUI_Spinner* yGridSpinner = glui->add_spinner_to_panel(vectorOptionsPanel,
                    "Grid size (Y)", GLUI_SPINNER_INT, NULL, i, yGridSizeSpinnerHandler);
                        
            
            xGridSpinner->set_int_limits(1, 100,GLUI_LIMIT_CLAMP);
            yGridSpinner->set_int_limits(1, 100,GLUI_LIMIT_CLAMP);
            xGridSpinner->set_int_val(20);
            yGridSpinner->set_int_val(20);
            xGridSpinners.push_back(xGridSpinner);
            yGridSpinners.push_back(yGridSpinner);
            
            
            
            normalizeCheckboxes.push_back(
                glui->add_checkbox_to_panel(vectorOptionsPanel,"Normalize",NULL,i,normalizeCheckboxHandler)
                );
            
            if (vis->getName().compare("Streamlines") == 0) {
                new GLUI_Button(vectorOptionsPanel, "Seed Grid", i, seedGridButtonHandler);
                new GLUI_Button(vectorOptionsPanel, "Set seed (e)", SeedButton, buttonHandler);
                
                removeSeedButtons.push_back(
                    glui->add_button_to_panel(vectorOptionsPanel, "Kill all seeds", i, removeSeedsButtonHandler)
                    );
                
            }            
            
            
        }
        else
        {
            vectorScaleSpinners.push_back(NULL);
            datasetListboxes.push_back(NULL);
            normalizeCheckboxes.push_back(NULL);
            xGridSpinners.push_back(NULL);
            yGridSpinners.push_back(NULL);
        }
        
        
        
        
        
        GLUI_Rollout *colorMapPanel = new GLUI_Rollout(currentRollout, "Colormap", true);
        
        colorMapPanel->set_alignment(GLUI_ALIGN_RIGHT);
        
        colorModeListboxes.push_back(
                makeListboxFromVector(vis->getColorModeNames(), "Colormap type", i, colorMapPanel, colorModeListboxHandler)
                );
        
        colorMapDatasetListboxes.push_back(
                makeListboxFromVector(vis->getColormapDatasets(), "Color dataset", i, colorMapPanel, colorMapDatasetListboxHandler)
                );
        
        alphaBlendCheckboxes.push_back(
                glui->add_checkbox_to_panel(colorMapPanel, "Use alpha blending", NULL, i, alphaBlendCheckboxHandler)
                );
                
                
        
        alphaDatasetListboxes.push_back(
                makeListboxFromVector(vis->getColormapDatasets(), "Alpha dataset", i, colorMapPanel, alphaDatasetListboxHandler)
                );
        
        scaleModeListboxes.push_back(
                makeListboxFromVector(vis->getScaleModeNames(), "Scale mode", i, colorMapPanel, scaleModeListboxHandler)
                );
        
        GLUI_Spinner* currentSpinner;
        // clamp maximum spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Clamp maximum", GLUI_SPINNER_FLOAT, NULL, i, clampMaxSpinnerHandler);
        currentSpinner->set_float_limits(clampMin, clampMax, clampFunction);
        currentSpinner->set_float_val(1.0);
        clampMaxSpinners.push_back(currentSpinner);
        
        //clamp minimum spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Clamp minimum", GLUI_SPINNER_FLOAT, NULL, i, clampMinSpinnerHandler);
        currentSpinner->set_float_limits(clampMin, clampMax, clampFunction);
        currentSpinner->set_float_val(0.0);
        clampMinSpinners.push_back(currentSpinner);
        
        //hue spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Colormap hue", GLUI_SPINNER_FLOAT, NULL, i, hueSpinnerHandler);
        currentSpinner->set_float_limits(hsvmin, hsvmax, clampFunction);
        currentSpinner->set_float_val(0.0);
        hueSpinners.push_back(currentSpinner);
       
        //saturation spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Colormap saturation", GLUI_SPINNER_FLOAT, NULL, i, satSpinnerHandler);
        currentSpinner->set_float_limits(hsvmin, hsvmax, clampFunction);
        currentSpinner->set_float_val(1.0);        
        satSpinners.push_back(currentSpinner);
        
        //value spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Colormap value", GLUI_SPINNER_FLOAT, NULL, i, valSpinnerHandler);
        currentSpinner->set_float_limits(hsvmin, hsvmax, clampFunction);
        currentSpinner->set_float_val(1.0);
        valSpinners.push_back(currentSpinner);
        
        //level spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Number of colors", GLUI_SPINNER_INT, NULL, i, levelSpinnerHandler);
        currentSpinner->set_int_limits(levelmin, levelmax, clampFunction);
        currentSpinner->set_int_val(100);
        levelSpinners.push_back(currentSpinner);
        colorMapPanel->close();
        currentRollout->close();
        
    }
   
    
}

GLUI_Listbox* Application::makeListboxFromVector(std::vector<std::string> vec, std::string name, int id, GLUI_Panel *panel, GLUI_Update_CB callBack)
{
    GLUI_Listbox* listbox = glui->add_listbox_to_panel(panel,name.c_str(),NULL,id,callBack);
    for (int i = 0; i < vec.size(); ++i)
    {
        const char *modename = vec.at(i).c_str();
        listbox->add_item(i, modename);
    }
    return listbox;
}

//display: Handle window redrawing events. Simply delegates to visualize().
void Application::display()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glEnable(GL_LIGHT0);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);
    glEnable(GL_NORMALIZE);
    glEnable(GL_COLOR_MATERIAL);    
    glEnable (GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    glLoadIdentity();
    
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_shininess[] = { 50.0 };
    GLfloat light_position[] = { 1000, 1000, 100.0, 0.8 };
    glShadeModel(GL_SMOOTH);

    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);    

    GLfloat ambientColor[] = {0.8, 0.8, 0.8, 1.0}; //Color(0.2, 0.2, 0.2)
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientColor);
    
    float width = 4.0f * float(winWidth) / winHeight;
    //float width = 4.0;
    float height = 4.0;
    
    glTranslatef(*xtrans, *ytrans, *ztrans);
    glTranslatef(0.0, 0.0, -1000.0);    
    glMultMatrixf(rotation);
     
    visualization.visualize(width, height, *timeslices, *slice_dt, *globalAlpha);
    
    glFlush();
    glutSwapBuffers();
}

void Application::resetView()
{
    *xtrans = defaultXTrans;
    *ytrans = defaultYTrans;
    *ztrans = defaultZTrans;
    *timeslices = defaultSlices;
    *slice_dt = defaultSliceDT;
    slicesSpinner->set_int_val(defaultSlices);
    slicesDTSpinner->set_int_val(defaultSliceDT);
    resetRotation();
   
}

void Application::resetRotation()
{
    for (int i = 0; i < 16; i++)
    {
        if (i % 5 == 0)
        {
            rotation[i] = 1.0;
        } else {
            rotation[i] = 0.0;
        }
    }
}

void Application::setWidgets()
{
}

void Application::quit()
{
    exit(0);
}

void Application::pause()
{
    simulation.toggle_frozen();
    if (simulation.frozen) {
        pause_button->set_name("Unfreeze");
    }
    else
    {
        pause_button->set_name("Freeze");
    }
}

